package tree;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.lang.tree.TreeNode;
import cn.hutool.core.lang.tree.TreeNodeConfig;
import cn.hutool.core.lang.tree.TreeUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.spire.ms.System.Collections.ArrayList;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;

import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

import static json.ParseJsonUtil.readFileContent;

/**
 * 【Hutool】树结构工具-TreeUtil
 * 官网：https://hutool.cn/docs/#/core/语言特性/树结构/树结构工具-TreeUtil
 * 【测试开发】八、接口测试-后端-模块树列表接口实现，使用HutoolUtil中TreeUtil快速处理树形结构
 * https://56data.cc/2106.html
 * https://www.cnblogs.com/pingguo-softwaretesting/p/15341673.html
 * https://developer.aliyun.com/article/1055132
 */
public class TreeUtil1 {

    // 菜单 关系型数据库数据  <->  Tree  <->  JSON
    // 每条数据根据parentId相互关联并表示层级关系，parentId在这里也叫外键
    public static void main(String[] args) {
        // 测试hutool 列转树
        simpleListToTreeHutool();

        // Java树形转扁平_多层嵌套map对象转扁平化map
        // Java扁平数据树型化
        // https://www.jianshu.com/p/ca11bf209daf

        // json文件地址 测试数据放在json文件里面
        String jsonFilePath = "java-tools\\src\\main\\java\\tree\\jsondata.json";
        // 树转列 将json数据(树)读取出来，再展开为列表
        List<JsonData> jsonData = parseTree(jsonFilePath);

        // 列转树 2种方式
        listToTreeMy(jsonData);
        listToTreeHutool(jsonData);

        /*[默认[9]
            默认-2[14]
            默认-1[10]
                默认-1-1[11]
                    默认-1-1-1[12]
                        默认-1-1-1-1[13]
        ]*/
        // 模糊查询，带出父节点 列查询列
        List<JsonData> matchJsonData = matchByName(jsonData, "默认-2");
        if (CollectionUtils.isNotEmpty(matchJsonData)) {
            // 列转树
            listToTreeHutool(matchJsonData);
        }

        matchJsonData = matchByName(jsonData, "默认-1-1-1");
        if (CollectionUtils.isNotEmpty(matchJsonData)) {
            // 列转树
            listToTreeHutool(matchJsonData);
        }

        matchJsonData = matchByName(jsonData, "默认-1-1-1-2");
        if (CollectionUtils.isNotEmpty(matchJsonData)) {
            // 列转树
            listToTreeHutool(matchJsonData);
        }

    }

    private static void simpleListToTreeHutool() {
        // 构建Tree
        // 构建node列表
        List<TreeNode<String>> nodeList = CollUtil.newArrayList();

        nodeList.add(new TreeNode<>("1", "0", "系统管理", 5));
        nodeList.add(new TreeNode<>("11", "1", "用户管理", 222222));
        nodeList.add(new TreeNode<>("111", "11", "用户添加", 0));
        nodeList.add(new TreeNode<>("2", "0", "店铺管理", 1));
        nodeList.add(new TreeNode<>("21", "2", "商品管理", 44));
        nodeList.add(new TreeNode<>("221", "2", "商品管理2", 2));

        // 0表示最顶层的id是0
        List<Tree<String>> treeList = TreeUtil.build(nodeList, "0");

        System.out.println(treeList);
    }

    /**
     * 列转树 自定义
     */
    private static void listToTreeMy(List<JsonData> jsonData) {
        List<JsonData> topNodes = new ArrayList();
        for (JsonData children : jsonData) {
            String pid = children.getParentId();
            if (pid == null || "0".equals(pid)) {
                // 顶级节点
                topNodes.add(children);
                continue;
            }
            // 子节点则遍历找父级，并添加到父级的子属性里面
            for (JsonData parent : jsonData) {
                String id = parent.getId();
                if (id != null && id.equals(pid)) {
                    List<JsonData> childList = parent.getChildren();
                    if (childList == null) {
                        childList = new ArrayList();
                        parent.setChildren(childList);
                    }
                    childList.add(children);
                    continue;
                }
            }
        }
        // 最后的顶级节点列表就是树 地址传值 引用传递
        System.out.println(topNodes);
    }

    /**
     * 列转树 hutool
     */
    private static void listToTreeHutool(List<JsonData> jsonData) {
        // 自定义字段名
        // 配置
        TreeNodeConfig treeNodeConfig = new TreeNodeConfig();
        // 自定义属性名，即返回列表里对象的字段名
        treeNodeConfig.setIdKey("id");
        treeNodeConfig.setWeightKey("pos");
        treeNodeConfig.setParentIdKey("parentId");
        treeNodeConfig.setChildrenKey("children");
        // 最大递归深度 可以不设，先不加限制
        // treeNodeConfig.setDeep(5);
        treeNodeConfig.setNameKey("name");
        //转换器
        List<Tree<String>> treeNodes = TreeUtil.build(jsonData, "0", treeNodeConfig,
                // NodeParser<T, E> nodeParser
                (treeNode, tree) -> {
                    tree.setId(treeNode.getId().toString());
                    tree.setParentId(treeNode.getParentId().toString());
                    tree.setWeight(treeNode.getPos());
                    tree.setName(treeNode.getName());
                    // 扩展属性
                    tree.putExtra("projectId", treeNode.getProjectId());
                    tree.putExtra("level", treeNode.getLevel());
                    tree.putExtra("label", treeNode.getName());
                    tree.putExtra("createTime", treeNode.getCreateTime());
                    tree.putExtra("updateTime", treeNode.getUpdateTime());
                });
        System.out.println(treeNodes);
    }

    /**
     * 树形扁平化
     * 从json文件读取，树-->列
     */
    public static List<JsonData> parseTree(String jsonFilePath) {

        // 保证文件存在
        File file = new File(jsonFilePath);
        if (!file.exists()) {
            System.out.println("json文件不存在");
        }
        String jsonString = readFileContent(file);
        if (jsonString == null || jsonString.trim().length() == 0) {
            System.out.println("文件内容为null");
        }

        // json字符串转实体类对象和实体类转json对象 fastjson
        // https://blog.csdn.net/SUMMERENT/article/details/129319281
        // json对象转实体类对象
        JSONObject dataJson = JSONObject.parseObject(jsonString);
        JSONArray dictData = dataJson.getJSONArray("data");
        // 多层嵌套子级也可以转化
        List<JsonData> treeList = JSONObject.parseArray(dictData.toString(), JsonData.class);
        System.out.println(treeList);

        // 解析树节点信息 扁平数据 层级 -> 扁平化
        // Java | 平铺列表(List)互转树形(Tree)结构
        // 递归
        // 通用方法 Java反射应用之树形转换为一层list数据结构  https://www.jianshu.com/p/79ff65229b0f
        // Java ：Tree 和 List 集合的相互转化 https://blog.csdn.net/xyjcfucdi128/article/details/103529434
        // java实现将树形结构平铺为List列表 https://blog.csdn.net/xuwenze/article/details/127899994

        // JAVA List和Tree互转 https://www.cnblogs.com/wl1202/p/15410147.html
        List<JsonData> lists = treeToList(treeList, null);
        System.out.println(lists);

        // java 8 数据树层级关系嵌套自定义排序模糊匹配生成和扁平化
        // https://blog.csdn.net/jsonms/article/details/124106301
        // return Stream.concat(
        //         //第一层流
        //         nodes.stream()
        //         ,nodes.stream().flatMap(node->
        //                 Stream.concat(
        //                         //第二层流
        //                         Optional.ofNullable(node.getChildren()).orElseGet(ArrayList::new).stream(),
        //                         //第三层流
        //                         Optional.ofNullable(node.getChildren()).orElseGet(ArrayList::new).stream()
        //                                 .flatMap(subNode->Optional.ofNullable(subNode.getChildren()).orElseGet(ArrayList::new).stream())
        //                 ))).map(this::covertNewNode).collect(Collectors.toList());

        return lists;
    }

    /**
     * 树形结构转list
     * JAVA List和Tree互转
     * https://www.cnblogs.com/wl1202/p/15410147.html
     */
    public static List<JsonData> treeToList(List<JsonData> depts, String pid) {

        List<JsonData> resultList = new ArrayList();

        for (JsonData ip : depts) {
            // 重新设置顶级节点pid
            // ip.setParentId(pid);
            if (ip.getChildren() != null && ip.getChildren().size() > 0) {
                // 递归 一直往下找子级
                resultList.addAll(treeToList(ip.getChildren(), ip.getId()));
                ip.setChildren(null);
                resultList.add(ip);
            } else {
                resultList.add(ip);
            }
        }
        return resultList;
    }

    /**
     * 数据转化为新的类，可通过其他工具替换数据转化类
     * @param node 原始节点
     * @return 新节点
     */
    // public Node covertNewNode(Node node){
    //     Node newNode = new Node();
    //     newNode.setId(node.getId());
    //     newNode.setName(node.getName());
    //     newNode.setParentId(node.getParentId());
    //     newNode.setOrderNo(node.getOrderNo());
    //     return newNode;
    // }


    /**
     * 通过名称匹配数据
     * Version 1.2 支持名称模糊匹配  https://blog.csdn.net/jsonms/article/details/124106301
     * java搜索树形结构数据,叶子节点并一起带父子节点数据返回 https://blog.csdn.net/Oscrazy/article/details/126411711
     * <p>
     * 节点名称模糊匹配好处理，处理难点在于关联节点的处理。
     * 首先拿到模糊匹配名称的节点，再扁平层级匹配关联节点，汇总流数据为集合数据，再调用生成层级树嵌套数据。
     *
     * @param nodeList 扁平化数据
     * @param nameKey  名称key
     * @return 匹配节点数据
     */
    public static List<JsonData> matchByName(List<JsonData> nodeList, String nameKey) {
        // list转Map
        Map<String, JsonData> nodeMap = nodeList.stream().collect(Collectors.toMap(JsonData::getId, Function.identity()));
        return nodeMap.entrySet().stream().filter(x -> StringUtils.upperCase(x.getValue().getName()).contains(nameKey.toUpperCase()))
                // flatMap 将多个流合并成了一个流
                .flatMap(x -> {
                    Map<String, JsonData> tempMap = new HashMap<>();
                    JsonData node = x.getValue();
                    String parentId;
                    do {
                        tempMap.put(node.getId(), node);
                        parentId = node.getParentId();
                        //寻找下一个父节点
                        node = nodeMap.get(parentId);
                    } while (!"0".equals(parentId));
                    // 数据组装传递
                    return tempMap.entrySet().stream();
                }).distinct().map(Map.Entry::getValue).collect(Collectors.toList());
    }

}
